#include "stdafx.h"
#include "texture2d.h"

#include <SDL_image.h>

namespace GLC {

#ifdef _WIN32
const char LAND_PATH[] = "..\\images\\land\\";
#else
const char LAND_PATH[] = "../images/land/";
#endif
static const std::string DIRECTORY = LAND_PATH;

class Texture2DOpenGLLoader
{
public:
    static Texture2DOpenGLLoader &instance()
    {
        static Texture2DOpenGLLoader loader;
        return loader;
    }

    void bind(GLuint texture)
    {
        if (_bindedTexture != texture)
        {
            _bindedTexture = texture;
            glBindTexture(GL_TEXTURE_2D, texture);
        }
    }

    void unbind()
    {
        _bindedTexture = 0;
        glBindTexture(GL_TEXTURE_2D, 0);
    }

    GLuint query(const std::string &name)
    {
        auto item = _loadedTextures.find(name);
        if (item != _loadedTextures.end())
        {
            auto &pair = item->second;
            ++pair.second;
            return pair.first;
        }
        {
            std::pair<GLuint, int> pair;
            pair.first = loadTexture(name);
            // There are one reference to texture object
            pair.second = 1;
            _loadedTextures[name] = pair;
            return pair.first;
        }
    }

    void release(const std::string &name)
    {
        auto item = _loadedTextures.find(name);
        if (item != _loadedTextures.end())
        {
            auto &pair = item->second;
            --pair.second;
            if (pair.second == 0)
            {
                if (pair.first == _bindedTexture)
                    unbind();
                glDeleteTextures(1, &pair.first);
                _loadedTextures.erase(item);
            }
        }
        else
        {
            myDebug() << "GLC::Texture2D error: attempt to delete non-existing texture"
                      << name << "\n";
        }
    }

private:
    GLuint loadTexture(const std::string &name)
    {
        std::string path = DIRECTORY + name;
        SDL_Surface* surface = IMG_Load(path.c_str());

        if (!surface)
        {
            myDebug() << "Texture file \'" << path
                      << "\' cannot be loaded\n";
            return 0;
        }
        GLuint texture;
        glPixelStorei(GL_UNPACK_ALIGNMENT,4);
        glGenTextures(1, &texture);
        glBindTexture(GL_TEXTURE_2D, texture);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

        // set anisotropic filtration level for this texture
        // (only if extension available)
//        if (GL_EXT_texture_filter_anisotropic)
//        {
//            GLint maxAnisotropy = 0;
//            glGetIntegerv(
//                GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT,
//                &maxAnisotropy);
//            glTexParameteri(
//                GL_TEXTURE_2D,
//                GL_TEXTURE_MAX_ANISOTROPY_EXT,
//                maxAnisotropy);
//        }

        SDL_PixelFormat * fmt = surface->format;

        if (fmt->Amask)
        {
            gluBuild2DMipmaps(GL_TEXTURE_2D, 4,
                              surface->w, surface->h,
                              GL_RGBA, GL_UNSIGNED_BYTE,
                              surface->pixels);
        }
        else
        {
            gluBuild2DMipmaps(GL_TEXTURE_2D, 3,
                              surface->w, surface->h,
                              GL_RGB, GL_UNSIGNED_BYTE,
                              surface->pixels);
        }
        SDL_FreeSurface(surface);

        glBindTexture(GL_TEXTURE_2D, _bindedTexture);

        return texture;
    }

    GLuint _bindedTexture;
    std::map<std::string,  std::pair<GLuint, int> > _loadedTextures;
};

Texture2D::Texture2D(const std::string &name)
    :_name(name)
{
    _texture = Texture2DOpenGLLoader::instance().query(_name);
}

Texture2D::~Texture2D()
{
    _texture = 0;
    Texture2DOpenGLLoader::instance().release(_name);
}

void Texture2D::bind() const
{
    Texture2DOpenGLLoader::instance().bind(_texture);
}

void Texture2D::unbind()
{
    Texture2DOpenGLLoader::instance().unbind();
}

Texture2DRef Texture2DRef::createNull()
{
    return Texture2DRef();
}

Texture2DRef::Texture2DRef(const Texture2D &source)
    :_texture(source._texture)
{
}

void Texture2DRef::bind() const
{
    Texture2DOpenGLLoader::instance().bind(_texture);
}

void Texture2DRef::unbind()
{
    Texture2DOpenGLLoader::instance().unbind();
}

bool Texture2DRef::isNull() const
{
    return (_texture == 0);
}

Texture2DRef::Texture2DRef()
    :_texture(0)
{
}

} // namespace GLC
